Skip to content

Deprecate now() in favour of execution.timestamp (#104) - #455

Merged
jeremymanning merged 1 commit into
feat/runtime-context-mainfrom
feat/deprecate-now-stacked
Aug 3, 2026
Merged

Deprecate now() in favour of execution.timestamp (#104)#455
jeremymanning merged 1 commit into
feat/runtime-context-mainfrom
feat/deprecate-now-stacked

Conversation

@jeremymanning

Copy link
Copy Markdown
Member

Reopens #453. Like #452, it merged into a stacked base that main had already taken, so it reported "merged" while its code never reached mainmain still has deprecated_for nowhere and now() in six examples. Same commit, correct base.

Stacked on #454, which reopens the RuntimeContext work against main. Review that first; this retargets to main when it merges.

What changed

now() reads the clock afresh at every use; execution.timestamp is computed once per run. Six catalogue examples used now() to stamp reports — precisely the case that wants one answer:

"timestamp": "{{ now() }}"          →  "{{ execution.timestamp }}"
*Report generated on: {{ now() }}*  →  *...{{ execution.timestamp }}*

A warning, not an error

now() keeps working. Pipelines outside this repo use it, and refusing them to make a style point would be a gratuitous break. Validation says so and names the replacement:

WARNING - Validation warning in template: 'now()' is deprecated: it is read
afresh at every use, so two steps of one run disagree. Use
'execution.timestamp', which is the same for every step.
✓ dep.yaml is valid

Exit 0, and it still runs. Verified through the CLI, not just the unit.

Chose a validation warning over a Python DeprecationWarning: pipeline authors write YAML, not Python, and DeprecationWarning is ignored in this project's pytest config — it would have been invisible to exactly the people who need it. The action-alias precedent uses the Python warning, so this is a deliberate divergence.

One declaration, three consumers

GlobalSpec.deprecated_for drives the message, the generated docs, and the catalogue guard rather than three restatements. find_global_misuse reports it only for a call that would otherwise work — now(1, 2) gets the arity error alone. One problem at a time.

Measured

before after
blocking suite 696 passed 700 passed, 0 failed
catalogue validating 30 / 117 30 / 117
examples calling now() 6 0

The count is flat because the six migrated files fail for unrelated reasons — this moves correctness, not the metric, which is the distinction worth keeping visible.

Mutations — three, all killed

mutation tests that failed
drop deprecated_for from now 2
make the deprecation an error 4
report it for a call that cannot work anyway 1

One flaw caught in my own guard: with no deprecated globals the regex became an empty alternation matching nearly every call, so the test would have reported the whole catalogue as offending. Guarded.

🤖 Generated with Claude Code

`now()` reads the clock afresh at every use, so two steps of one run get
different answers. `execution.timestamp` is computed once per run. Six
catalogue examples stamped their reports with `now()`, which is exactly the
case where one answer is wanted:

    "timestamp": "{{ now() }}"        ->  "{{ execution.timestamp }}"
    *Report generated on: {{ now() }}*

A warning, not an error. `now()` keeps working -- pipelines outside this
repository use it, and refusing them to make a style point would be a
gratuitous break -- but validation now names the replacement:

    'now()' is deprecated: it is read afresh at every use, so two steps of
    one run disagree. Use 'execution.timestamp', which is the same for
    every step.

The pipeline still validates (exit 0) and still runs.

`GlobalSpec` carries `deprecated_for`, so the message, the generated
documentation and the catalogue guard all come from one declaration rather
than three restatements. `find_global_misuse` reports it only for a call
that would otherwise work: a wrong-arity `now(1, 2)` gets the arity error
alone, one problem at a time.

`TemplateValidationError` already carried a severity; the validator now
routes on it, so a warning goes to `warnings` and lets the run proceed while
an error still refuses it.

Measured: blocking suite 696 -> 700 passed, 0 failed. Catalogue unchanged at
30/117 -- the six migrated files fail for unrelated reasons, so this moves
correctness rather than the count.

Three mutations, all killed:
  - drop `deprecated_for` from `now`             -> 2 tests
  - make the deprecation an error                -> 4 tests
  - report it for a call that cannot work anyway -> 1 test

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jeremymanning
jeremymanning merged commit 90cc758 into feat/runtime-context-main Aug 3, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant